package cn.keniykey.mc.landterritory.land.api;

import java.lang.reflect.Type;
import java.util.Random;

import javax.annotation.Nullable;

import org.bukkit.Location;
import org.bukkit.block.BlockFace;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;

public final class LandRange implements Cloneable {
	private transient static Gson gson;
	private static class LandRangeJsonGosn implements JsonSerializer<LandRange>,JsonDeserializer<LandRange>{
		@Override
		public LandRange deserialize(JsonElement jsonen, Type type, JsonDeserializationContext josnd)
				throws JsonParseException {
			final JsonObject jobj = jsonen.getAsJsonObject();
			final LandPoint point_min = LandPoint.gson.fromJson(jobj.get("point1"), LandPoint.class);
			final LandPoint point_max = LandPoint.gson.fromJson(jobj.get("point2"), LandPoint.class);
			return new LandRange(point_min, point_max);
		}
		@Override
		public JsonElement serialize(LandRange range, Type type, JsonSerializationContext josns) {
			final JsonObject jobj = new JsonObject();
			jobj.add("point1", LandPoint.gson.toJsonTree(range.point_min));
			jobj.add("point2", LandPoint.gson.toJsonTree(range.point_max));
			return jobj;
		}
	}
	
	static {
		gson = new GsonBuilder().registerTypeAdapter(LandRange.class, new LandRangeJsonGosn()).create();
	}
	
	private LandPoint point_min,point_max;
	
	public LandRange(LandPoint point_min,LandPoint point_max) {
		this.point_min = point_min; this.point_max = point_max;
	}
	public LandRange() {
		this(null,null);
	}
	
	public String toJson() {
		return gson.toJson(this);
	}
	public static LandRange FromJson(String json) {
		return gson.fromJson(json, LandRange.class);
	}
	
	@Nullable
	public String getWorldName() {
		if(this.point_min != null) return this.point_min.getWorldName();
		if(this.point_max != null) return this.point_max.getWorldName();
		return null;
	}
	@Nullable
	public LandPoint getLandPointOne() {
		return this.point_min;
	}
	public void setLandPointOne(LandPoint point) {
		this.point_min = point;
	}
	@Nullable
	public LandPoint getLandPointTwo() {
		return this.point_max;
	}
	public void setLandPointTwo(LandPoint point) {
		this.point_max = point;
	}
	/**
	 * 判断领地范围是否合理许可（有两个定点且都在相同世界）
	 */
	public boolean isRangeAllow() {
		if(this.point_min == null || this.point_max == null) return false;
		try {
			return this.point_min.getWorldName().equals(this.point_max.getWorldName());
		}catch(Exception ex) {
			ex.printStackTrace();
		}
		return false;
	}
	/**
	 * 获取X轴长度
	 */
	public int getXLength() {
		return Math.abs(this.point_max.getX() - this.point_min.getX()) + 1;
	}
	/**
	 * 获取Y轴长度
	 */
	public int getYLength() {
		return Math.abs(this.point_max.getY() - this.point_min.getY()) + 1;
	}
	/**
	 * 获取Z轴长度
	 */
	public int getZLength() {
		return Math.abs(this.point_max.getZ() - this.point_min.getZ()) + 1;
	}
	/**
	 * 获取方体xyz轴总周长（4个xl，4个yl,4个zl）
	 */
	public long getTotalLength() {
		return this.getXLength() * 4 + this.getYLength() * 4 + this.getZLength() * 4;
	}
	/**
	 * 获取方体体积，总方块数（x*y*z）
	 */
	public long getSize() {
		final long xz = (this.getXLength() * this.getZLength());
		final long size = (xz * this.getYLength());
		return size < 0 ? Long.MAX_VALUE : size;
	}
	/**
	 * 获取X轴中心点（正负敏感）
	 */
	public int getXCenter() {
		return (int) ((this.point_min.getX() + this.point_max.getX()) / 2);
	}
	/**
	 * 获取Y轴中心轴（正负敏感）
	 */
	public int getYCenter() {
		return(int) ((this.point_min.getY() + this.point_max.getY()) / 2);
	}
	/**
	 * 获取Z轴中心轴（正负敏感）
	 */
	public int getZCenter() {
		return (int) ((this.point_min.getZ() + this.point_max.getZ()) / 2);
	}
	/**
	 * 获取中心坐标（调用XYZ中心轴）
	 */
	public LandPoint getCenterPoint() {
		return new LandPoint(this.getWorldName(), this.getXCenter(), this.getYCenter(), this.getZCenter());
	}
	private int getRandom(int min_min,int max_max) {
		final int min = Math.min(min_min, max_max), max = Math.max(min_min, max_max);
		return min + new Random().nextInt(max - min);
	}
	/**
	 * 获取范围内随机定点
	 */
	public LandPoint getRandomPoint() {
		final int x = this.getRandom(this.point_min.getX(), this.point_max.getX());
		final int y = this.getRandom(this.point_min.getY(), this.point_max.getY());
		final int z = this.getRandom(this.point_min.getZ(), this.point_max.getZ());
		return new LandPoint(this.getWorldName(), x, y, z);
	}
	/**
	 * 判断这个范围是否在指定范围内
	 */
	public boolean isInRange(String worldname,int x,int y,int z) {
		if(!worldname.equals(this.getWorldName())) return false; //min<x<max,one<x<two
		if(this.point_min.getX() > x || x > this.point_max.getX()) return false;
		if(this.point_min.getY() > y || y > this.point_max.getY()) return false;
		if(this.point_min.getZ() > z || z > this.point_max.getZ()) return false;
		return true;
	}
	public boolean isInRange(Location loc) {
		return this.isInRange(loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
	}
	public boolean isInRange(LandPoint point) {
		return this.isInRange(point.getWorldName(), point.getX(), point.getY(), point.getZ());
	}
	public boolean isInRange(LandPosition pos) {
		return this.isInRange(pos.toLocation());
	}
	public boolean isInRange(LandRange range) {
		if(range.getLandPointOne() == null || range.getLandPointTwo() == null) return false;
		return (this.isInRange(range.getLandPointOne()) && this.isInRange(range.getLandPointTwo()));
	}
	/**
	 * 判断范围是否与范围重叠
	 */
	public boolean cover(LandRange range) {
		if (range == null) return false;
		if (!this.getWorldName().equals(range.getWorldName())) return false;
		if (!(this.getXLength() + range.getXLength() > Math.abs(this.point_min.getX() + this.point_max.getX() - range.point_min.getX() - range.point_max.getX()))) return false;
		if (!(this.getYLength() + range.getYLength() > Math.abs(this.point_min.getY() + this.point_max.getY() - range.point_min.getY() - range.point_max.getY()))) return false;
		if (!(this.getZLength() + range.getZLength() > Math.abs(this.point_min.getZ() + this.point_max.getZ() - range.point_min.getZ() - range.point_max.getZ()))) return false;
		return true;
	}
	/**
	 * 将范围两个定点规范化（点1最小，点2最大）（重要）
	 */
	public void fit() {
		final int x1 = this.point_min.getX(),y1 = this.point_min.getY(),z1 = this.point_min.getZ();
		final int x2 = this.point_max.getX(),y2 = this.point_max.getY(),z2 = this.point_max.getZ();
		if (x1 > x2) {
			this.point_min.setX(x2);
			this.point_max.setX(x1);
		}
		if (y1 > y2) {
			this.point_min.setY(y2);
			this.point_max.setY(y1);
		}
		if (z1 > z2) {
			this.point_min.setZ(z2);
			this.point_max.setZ(z1);
		}
	}
	/**
	 * 扩展范围，在当前数据上进行
	 */
	public void expand(final BlockFace face,final int grid) {
		if(!this.isRangeAllow()) throw new IllegalStateException("The data is not up to specification(数据不符合规范)");
		this.fit();
		switch(face) {
		case UP: //y+
			this.point_max.setY((this.point_max.getY() + grid));
			break;
		case DOWN: //y-
			this.point_min.setY((this.point_min.getY() - grid));
			break;
		case EAST: //x+
			this.point_max.setX((this.point_max.getX() + grid));
			break;
		case WEST: //x-
			this.point_min.setX((this.point_min.getX() - grid));
			break;
		case SOUTH: //z+
			this.point_max.setZ((this.point_max.getZ() + grid));
			break;
		case NORTH:
			this.point_min.setZ((this.point_min.getZ() - grid));
			break;
		default:
			throw new IllegalArgumentException("Incompatible Face(不兼容Face):" + face.toString());
		}
	}
	/**
	 * 缩小范围，在当前数据上进行
	 */
	public boolean reduce(final BlockFace face,final int grid) {
		if(!this.isRangeAllow()) throw new IllegalStateException("The data is not up to specification(数据不符合规范)");
		this.fit();
		switch(face) {
		case UP: //y+1
			if(this.getYLength() < grid) return false;
			this.point_max.setY((this.point_min.getY() - grid));
			break;
		case DOWN: //y-1
			if(this.getYLength() < grid) return false;
			this.point_min.setY((this.point_min.getY() + grid));
			break;
		case EAST: //x+1
			if(this.getXLength() < grid) return false;
			this.point_max.setX((this.point_max.getX() - grid));
			break;
		case WEST: //x-1
			if(this.getXLength() < grid) return false;
			this.point_min.setX((this.point_min.getX() + grid));
			break;
		case SOUTH: //z+1
			if(this.getZLength() < grid) return false;
			this.point_max.setZ((this.point_max.getZ() - grid));
			break;
		case NORTH: //z-1
			if(this.getZLength() < grid) return false;
			this.point_min.setZ((this.point_min.getZ() + grid));
			break;
		default:
			throw new IllegalArgumentException("Incompatible Face(不兼容Face):" + face.toString());
		}
		return true;
	}
	
	@Override
	public LandRange clone() {
		return new LandRange(this.point_min.clone(), this.point_max.clone());
	}
	@Override
	public int hashCode() {
		return this.point_min.hashCode() * 31 + this.point_max.hashCode();
	}
	@Override
	public boolean equals(Object obj) {
		if(obj == null) return false;
		if(obj == this) return true;
		if(!(obj instanceof LandRange)) return false;
		final LandRange range = (LandRange) obj;
		return (this.point_min.equals(range.point_min) && this.point_max.equals(range.point_max));
	}
	@Override
	public String toString() {
		return "[point1:" + this.point_min.toString() + " point2:" + this.point_max.toString() + "]";
	}
}